/**
 * Copyright 2015 Hannes Dorfmann.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hannesdorfmann.mosby3.mvp.delegate;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilityLifecycleCallbacks;
import ohos.aafwk.ability.AbilitySlice;
import ohos.agp.components.Component;
import ohos.app.Context;
import ohos.app.ElementsCallback;
import ohos.global.configuration.Configuration;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.PacMap;
import ohos.utils.Sequenceable;

import com.hannesdorfmann.mosby3.PresenterManager;
import com.hannesdorfmann.mosby3.mvp.MvpPresenter;
import com.hannesdorfmann.mosby3.mvp.MvpView;
import com.hannesdorfmann.mosby3.mvp.viewstate.ViewState;

import java.util.UUID;

/**
 * A {@link ComponentContainerMvpDelegate} that supports {@link ViewState}
 *
 * @author Hannes Dorfmann
 */
public class ComponentContainerMvpViewStateDelegateImpl<V extends MvpView, P extends MvpPresenter<V>, VS extends ViewState<V>>
        implements ComponentContainerMvpDelegate<V, P>, AbilityLifecycleCallbacks, ElementsCallback /*Application.ActivityLifecycleCallbacks*/ {

    private static final String TAG = "ViewGroupMvpViewStateDe";
    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, TAG);

    // TODO allow custom save state hook in

    public final static boolean DEBUG = false;
    private ComponentContainerMvpViewStateDelegateCallback<V, P, VS> delegateCallback;
    private String mosbyViewId;
    private final boolean keepPresenterDuringScreenOrientationChange;
    private final Ability activity;

    private boolean checkedActivityFinishing = false;
    private boolean presenterDetached = false;
    private boolean presenterDestroeyed = false;

    private VS restoreableParcelableViewState = null;

    private boolean applyViewState = false;
    private boolean viewStateFromMemoryRestored = false;

    protected AbilitySlice mAbilitySlice;

    private static final String BASE_URL = "https://github.com/sockeqwe/mosby";
    private static final String ISSURE = "issues";

    /**
     * Creates a new instance
     *
     * @param delegateCallback                           the callback
     * @param keepPresenterDuringScreenOrientationChange true, if you want to keep the presenter and view state during screen
     *                                                   orientation changes etc.
     */
    public ComponentContainerMvpViewStateDelegateImpl(Component view,
                                                      ComponentContainerMvpViewStateDelegateCallback<V, P, VS> delegateCallback,
                                                      boolean keepPresenterDuringScreenOrientationChange) {
        if (view == null) {
            throw new NullPointerException("View is null!");
        }

        if (delegateCallback == null) {
            throw new NullPointerException("MvpDelegateCallback is null!");
        }

        this.delegateCallback = delegateCallback;
        this.keepPresenterDuringScreenOrientationChange = keepPresenterDuringScreenOrientationChange;

        this.activity = PresenterManager.getActivity(delegateCallback.getmComponetContext());
        this.activity.getAbilityPackage().registerCallbacks(this, this);

    }

    /**
     * Generates the unique (mosby internal) viewState id and calls {@link
     * MvpDelegateCallback#createPresenter()}
     * to create a new presenter instance
     *
     * @return The new created presenter instance
     */
    private P createViewIdAndCreatePresenter(AbilitySlice mAbilitySlice) {

        P presenter = delegateCallback.createPresenter();
        if (presenter == null) {
            throw new NullPointerException("Presenter returned from createPresenter() is null.");
        }
        if (keepPresenterDuringScreenOrientationChange) {
            Context context = delegateCallback.getmComponetContext();

            mosbyViewId = UUID.randomUUID().toString();
            PresenterManager.putPresenter(PresenterManager.getActivity(context), mosbyViewId, presenter);
        }
        return presenter;
    }

    private VS createViewState() {
        VS viewState = delegateCallback.createViewState();
        if (keepPresenterDuringScreenOrientationChange) {
            PresenterManager.putViewState(activity, mosbyViewId, viewState);
        }
        applyViewState = false;
        viewStateFromMemoryRestored = false;
        return viewState;
    }

    //  /**
//   * Must be called from {@link View#onSaveInstanceState()}
//   */
    @Override
    public Sequenceable onSaveInstanceState() {

        VS viewState = delegateCallback.getViewState();
        if (viewState == null) {
            throw new NullPointerException("ViewState returned from getViewState() is null for MvpView "
                    + delegateCallback.getMvpView());
        }

        Sequenceable superState = delegateCallback.superOnSaveInstanceState();
        return superState;
    }

    //  /**
//   * Must be called from {@link View#onRestoreInstanceState(Parcelable)}
//   */
    @Override
    public void onRestoreInstanceState(Sequenceable state) {
        if (!(state instanceof MosbyViewStateSavedState)) {
            delegateCallback.superOnRestoreInstanceState(state);
            return;
        }

        MosbyViewStateSavedState savedState = (MosbyViewStateSavedState) state;
    }


    @Override
    public void onAttachedToWindow() {

        P presenter = null;
        VS viewState = null;
        if (mosbyViewId == null) {
            // No presenter available,
            // Ability is starting for the first time (or keepPresenterInstance == false)
            presenter = createViewIdAndCreatePresenter(mAbilitySlice);
            if (DEBUG) {
                HiLog.error(LABEL, "new Presenter instance created: " + presenter);
            }

            viewState = createViewState();
            if (DEBUG) {
                HiLog.error(LABEL, "New ViewState instance created: "
                        + viewState
                        + " MvpView: "
                        + delegateCallback.getMvpView());
            }
        } else {
            presenter = PresenterManager.getPresenter(activity, mosbyViewId);
            if (presenter == null) {
                // Process death,
                // hence no presenter with the given viewState id stored, although we have a viewState id
                presenter = createViewIdAndCreatePresenter(mAbilitySlice);
                if (DEBUG) {
                    HiLog.error(LABEL,
                            "No Presenter instance found in cache, although MosbyView ID present. This was caused by process death, therefore new Presenter instance created: "
                                    + presenter);
                }
            } else {
                if (DEBUG) {
                    HiLog.error(LABEL, "Presenter instance reused from internal cache: " + presenter);
                }
            }

            viewState = PresenterManager.getViewState(activity, mosbyViewId);
            if (viewState == null) {

                if (restoreableParcelableViewState == null) {
                    // Process death, no viewstate restored from parcel
                    viewState = createViewState();
                    if (DEBUG) {
                        HiLog.error(LABEL,
                                "No ViewState instance found in cache, although MosbyView ID present. This was caused by process death, therefore new ViewState instance created: "
                                        + viewState);
                    }
                } else {
                    // Memory ViewState is null, so use RestoreableViewState
                    viewState = restoreableParcelableViewState;
                    applyViewState = true;
                    viewStateFromMemoryRestored = false;

                    if (keepPresenterDuringScreenOrientationChange) {
                        if (mosbyViewId == null) {
                            StringBuilder sb = new StringBuilder();
                            sb.append(BASE_URL);
                            sb.append(ISSURE);
                            throw new IllegalStateException(
                                    "The (internal) Mosby View id is null although restoreable view state (Parcelable) is not null. This should never happen. This seems to be a Mosby internal error. Please report this issue at "+sb.toString());
                        }
                        // Put restoreable view state into memory cache for future useage
                        PresenterManager.putViewState(activity, mosbyViewId, viewState);
                    }

                    if (DEBUG) {
                        HiLog.error(LABEL, "Parcelable ViewState instance reused from last SavedState: "
                                + viewState
                                + " MvpView: "
                                + delegateCallback.getMvpView());
                    }
                }
            } else {
                // ViewState from memory
                applyViewState = true;
                viewStateFromMemoryRestored = true;
                if (DEBUG) {
                    HiLog.error(LABEL, "ViewState instance reused from internal cache: "
                            + viewState
                            + " MvpView: "
                            + delegateCallback.getMvpView());
                }
            }
        }

        // presenter is ready, so attach viewState
        V view = delegateCallback.getMvpView();
        if (view == null) {
            throw new NullPointerException(
                    "MvpView returned from getMvpView() is null. Returned by " + delegateCallback);
        }

        if (presenter == null) {
            StringBuilder sb = new StringBuilder();
            sb.append(BASE_URL);
            sb.append(ISSURE);
            throw new IllegalStateException(
                    "Oops, Presenter is null. This seems to be a Mosby internal bug. Please report this issue here: "+sb.toString());
        }


        delegateCallback.setPresenter(presenter);
        delegateCallback.setViewState(viewState);

        if (applyViewState) {
            delegateCallback.setRestoringViewState(true);
            viewState.apply(view, viewStateFromMemoryRestored);
            delegateCallback.setRestoringViewState(false);
            presenter.attachView(view);
            delegateCallback.onViewStateInstanceRestored(viewStateFromMemoryRestored);
        } else {
            presenter.attachView(view);
            delegateCallback.onNewViewStateInstance();
        }

        if (DEBUG) {
            HiLog.error(LABEL,
                    "MvpView attached to Presenter. MvpView: " + view + "   Presenter: " + presenter);
        }
    }

    @Override
    public void onDetachedFromWindow() {
        detachPresenterIfNotDoneYet();

        if (!checkedActivityFinishing) {

            boolean destroyPermanently = !AbilityMvpDelegateImpl.retainPresenterInstance(
                    keepPresenterDuringScreenOrientationChange, activity);

            if (destroyPermanently) {
                destroyPresenterIfnotDoneYet();
            } else if (!activity.isUpdatingConfigurations()) {
                // View removed manually from screen
                destroyPresenterIfnotDoneYet();
            }
        } // else --> see onActivityDestroyed()
    }


    private void destroyPresenterIfnotDoneYet() {
        if (!presenterDestroeyed) {
            P presenter = delegateCallback.getPresenter();
            presenter.destroy();
            presenterDestroeyed = true;
            activity.getAbilityPackage().unregisterCallbacks(this, this);
            if (DEBUG) {
                HiLog.error(LABEL, "Presenter destroyed: " + presenter);
            }

            if (mosbyViewId != null) {
                // mosbyViewId == null if keepPresenterDuringScreenOrientationChange == false
                PresenterManager.remove(activity, mosbyViewId);
            }
            mosbyViewId = null;
        }
    }

    private void detachPresenterIfNotDoneYet() {
        if (!presenterDetached) {
            P presenter = delegateCallback.getPresenter();
            presenter.detachView();
            presenterDetached = true;
            if (DEBUG) {
                HiLog.error(LABEL,
                        "view " + delegateCallback.getMvpView() + " detached from Presenter " + presenter);
            }
        }
    }

    @Override
    public void onAbilityStart(Ability ability) {

    }

    @Override
    public void onAbilityActive(Ability ability) {

    }

    @Override
    public void onAbilityInactive(Ability ability) {

    }

    @Override
    public void onAbilityForeground(Ability ability) {

    }

    @Override
    public void onAbilityBackground(Ability ability) {

    }

    @Override
    public void onAbilityStop(Ability ability) {

    }

    @Override
    public void onAbilitySaveState(PacMap pacMap) {

    }

    @Override
    public void onMemoryLevel(int i) {

    }

    @Override
    public void onConfigurationUpdated(Configuration configuration) {

    }
}
